#include <stdlib.h>
#include <pthread.h>
#include <stdarg.h>
#include <string.h>

#include "psd_event.h"
#include "psd_logger.h"

#include "queue.h"

static int psd_event_verify_expression(const char *ev_exp);
static int psd_event_compare_event_id(const char *ev_exp, const char *ev);

typedef struct psd_event_handler_t {
	char *event_exp;
	event_handler func;
} psdEventHandler;

psdEventHandler **event_listeners;
int event_listeners_count;
pthread_mutex_t event_listeners_lock;

int voidlist_add(void *val, void ***list, int *list_length) {
	void **new_list;

	new_list = realloc(*list, sizeof(void *) * (*list_length + 1));
	if (!new_list)
		goto error_exit;

	new_list[*list_length] = val;
	*list = new_list;
	*list_length = *list_length + 1;

	return 0;
error_exit:
	return -1;
}

int psd_event_init() {
	if (pthread_mutex_init(&event_listeners_lock, NULL) != 0) {
		goto error_exit;
	}

	event_listeners = NULL;
	event_listeners_count = 0;

	return 0;

error_exit:
	return -1;
}

psdEventHandler *psd_event_handler_alloc(const char *event_exp, event_handler f) {
	psdEventHandler *handler;

	handler = malloc(sizeof(psdEventHandler));
	if (!handler) {
		psd_err(0, "Couldn't allocate event handler");
		goto error_exit;
	}

	handler->event_exp = strdup(event_exp);
	if (!handler->event_exp) {
		psd_err(0, "Couldn't allocate event handler");
		goto error_exit_handler;
	}

	handler->func = f;

	return handler;

error_exit_handler:
	free(handler);
error_exit:
	return NULL;
}

int psd_event_register_listener(const char *event_exp, event_handler f) {
	int retval;
	psdEventHandler *handler;

	if (psd_event_verify_expression(event_exp) == 0) {
		psd_err(0, "Invalid listener expression: %s", event_exp);
		goto error_exit;
	}

	handler = psd_event_handler_alloc(event_exp, f);
	if (!handler) {
		psd_err(0, "Couldn't allocate event handler structure");
		goto error_exit;
	}

	pthread_mutex_lock(&event_listeners_lock);
	{
		retval = voidlist_add(handler, (void ***) &event_listeners, &event_listeners_count);
	}
	pthread_mutex_unlock(&event_listeners_lock);

	return retval;

error_exit:
	return -1;
}

static int psd_event_verify_expression(const char *ev_exp) {
	int i;

	for(i = 0; ev_exp[i] != '\0'; i++) {
		// if we have a '*', it must be the last thing in the string.
		if (ev_exp[i] == '*' && ev_exp[i+1] != '\0') {
			return 0;
		}
	}

	return 1;
}

static int psd_event_compare_event_id(const char *ev_exp, const char *ev) {
	int i;

	i = 0;
	while(ev_exp[i] == ev[i] && ev_exp[i] != '\0' && ev[i] != '\0') {
		i++;
	}

	// have we hit the end of one or both strings
	if (ev_exp[i] == '\0' && ev[i] == '\0') {
		return 1;
	}

	// it looks like:
	// event expression:  one.event*
	// event:             one.event
	if (ev[i] == '\0' && ev_exp[i] == '*') {
		return 1;
	}

	// it looks like:
	// event expression:  one.eventname
	// event:             one.event
	if (ev[i] == '\0') {
		return 0;
	}

	// it looks like:
	// event expression:  one.event
	// event:             one.eventname
	if (ev_exp[i] == '\0') {
		return 0;
	}

	// it looks like:
	// event expression:  one.event*
	// event:             one.eventname
	if (ev_exp[i] == '*') {
		return 1;
	}

	return 0;
}

void psd_event(const char *event_id, void *arg, const char *desc, ...) {
	int i;
	char filled_description[8192];
	int filled;
	int n;

	filled = 0;

	pthread_mutex_lock(&event_listeners_lock);
	{
		for(i = 0; i < event_listeners_count; i++) {
			psdEventHandler *handler = event_listeners[i];
			if (psd_event_compare_event_id(handler->event_exp, event_id)) {
				if (!filled) {
					va_list argp;

					va_start(argp, desc);
					vsnprintf(filled_description, sizeof(filled_description), desc, argp);
					va_end(argp);
				}

				if (handler->func(event_id, arg, filled_description) != 0)
					break;
			}
		}
	}
	pthread_mutex_unlock(&event_listeners_lock);
}
